﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Runtime.InteropServices;
using Microsoft.Win32;

namespace Folder_Control
{
    public class Icons
    {
        [DllImport("comctl32")]
        private extern static IntPtr ImageList_GetIcon(IntPtr himl, int i, int flags);

        [DllImport("shell32.dll", EntryPoint = "#727")]
        private extern static int SHGetImageList(int iImageList, ref Guid riid, ref IImageList ppv);

        [DllImport("shell32.dll", EntryPoint = "#727")]
        private extern static int SHGetImageListHandle(int iImageList, ref Guid riid, ref IntPtr handle);

        [DllImport("shell32")]
        private static extern IntPtr SHGetFileInfo(string pszPath, int dwFileAttributes, ref SHFILEINFO psfi, uint cbFileInfo, uint uFlags);

        // Uses pidl instead of path (Used for Special Folders)
        [DllImport("shell32")]
        private static extern IntPtr SHGetFileInfo(IntPtr pidl, int dwFileAttributes, ref SHFILEINFO psfi, uint cbFileInfo, uint uFlags);

        [DllImport("shell32.dll", SetLastError = true)]
        static extern int SHGetSpecialFolderLocation(IntPtr hwndOwner, CSIDL nFolder, ref IntPtr ppidl);

        #region Constants
        private const int MAX_PATH = 260;
        private const int FILE_ATTRIBUTE_NORMAL = 0x80;
        private const int FILE_ATTRIBUTE_DIRECTORY = 0x10;
        #endregion

        #region Fields
        private IntPtr hIml = IntPtr.Zero;
        private IImageList iImageList = null;
        private SystemImageListSize size = SystemImageListSize.SmallIcons;
        private bool disposed = false;
        #endregion

        [StructLayout(LayoutKind.Sequential)]
        private struct SHFILEINFO
        {
            public IntPtr hIcon;
            public int iIcon;
            public int dwAttributes;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH)]
            public string szDisplayName;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
            public string szTypeName;
        }

        #region Private ImageList COM Interop (XP)
        [ComImportAttribute()]
        [GuidAttribute("46EB5926-582E-4017-9FDF-E8998DAA0950")]
        [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
        //helpstring("Image List"),
        interface IImageList
        {
            [PreserveSig]
            int Add(IntPtr hbmImage, IntPtr hbmMask, ref int pi);

            [PreserveSig]
            int ReplaceIcon(int i, IntPtr hicon, ref int pi);

            [PreserveSig]
            int SetOverlayImage( int iImage, int iOverlay);

            [PreserveSig]
            int Replace(int i, IntPtr hbmImage, IntPtr hbmMask);

            [PreserveSig]
            int AddMasked(IntPtr hbmImage, int crMask, ref int pi);

            [PreserveSig]
            int Draw(ref IMAGELISTDRAWPARAMS pimldp);

            [PreserveSig]
            int Remove(int i);

            [PreserveSig]
            int GetIcon(int i, int flags, ref IntPtr picon);

            [PreserveSig]
            int GetImageInfo(int i, ref IMAGEINFO pImageInfo);

            [PreserveSig]
            int Copy(int iDst, IImageList punkSrc, int iSrc, int uFlags);

            [PreserveSig]
            int Merge(int i1, IImageList punk2, int i2, int dx, int dy, ref Guid riid, ref IntPtr ppv);

            [PreserveSig]
            int Clone(ref Guid riid, ref IntPtr ppv);

            [PreserveSig]
            int GetImageRect(int i, ref RECT prc);

            [PreserveSig]
            int GetIconSize(ref int cx, ref int cy);

            [PreserveSig]
            int SetIconSize(int cx, int cy);

            [PreserveSig]
            int GetImageCount(ref int pi);

            [PreserveSig]
            int SetImageCount(int uNewCount);

            [PreserveSig]
            int SetBkColor(int clrBk, ref int pclr);

            [PreserveSig]
            int GetBkColor(ref int pclr);

            [PreserveSig]
            int BeginDrag(int iTrack, int dxHotspot, int dyHotspot);

            [PreserveSig]
            int EndDrag();

            [PreserveSig]
            int DragEnter(IntPtr hwndLock, int x, int y);

            [PreserveSig]
            int DragLeave(IntPtr hwndLock);

            [PreserveSig]
            int DragMove(int x, int y);

            [PreserveSig]
            int SetDragCursorImage(ref IImageList punk, int iDrag, int dxHotspot, int dyHotspot);

            [PreserveSig]
            int DragShowNolock(int fShow);

            [PreserveSig]
            int GetDragImage(ref Point ppt, ref Point pptHotspot,ref Guid riid, ref IntPtr ppv);

            [PreserveSig]
            int GetItemFlags(int i, ref int dwFlags);

            [PreserveSig]
            int GetOverlayImage(int iOverlay, ref int piIndex);
        };

        #region Other Parameters
            [StructLayout(LayoutKind.Sequential)]
            private struct IMAGELISTDRAWPARAMS
            {
                public int cbSize;
                public IntPtr himl;
                public int i;
                public IntPtr hdcDst;
                public int x;
                public int y;
                public int cx;
                public int cy;
                public int xBitmap;        // x offest from the upperleft of bitmap
                public int yBitmap;        // y offset from the upperleft of bitmap
                public int rgbBk;
                public int rgbFg;
                public int fStyle;
                public int dwRop;
                public int fState;
                public int Frame;
                public int crEffect;
            }

            [StructLayout(LayoutKind.Sequential)]
            private struct RECT
            {
                int left;
                int top;
                int right;
                int bottom;
            }

            [StructLayout(LayoutKind.Sequential)]
            private struct IMAGEINFO
            {
                public IntPtr hbmImage;
                public IntPtr hbmMask;
                public int Unused1;
                public int Unused2;
                public RECT rcImage;
            }
            #endregion
        #endregion

        #region Public Enumerations
        /// <summary>
        /// Available system image list sizes
        /// </summary>
        public enum SystemImageListSize : int
        {
            /// <summary>
            /// System Large Icon Size (typically 32x32)
            /// </summary>
            LargeIcons = 0x0,
            /// <summary>
            /// System Small Icon Size (typically 16x16)
            /// </summary>
            SmallIcons = 0x1,
            /// <summary>
            /// System Extra Large Icon Size (typically 48x48).
            /// </summary>
            ExtraLargeIcons = 0x2,
            /// <summary>
            /// Size specified by GetSystemMetrics (Usually smaller than SmallIcons)
            /// </summary>
            SystemSmallIcons = 0x3,
            /// <summary>
            /// System Jumbo Icon Size (typically 256x256).
            /// </summary>
            JumboIcons = 0x4
        }

        /// <summary>
        /// Flags controlling how the Image List item is 
        /// drawn
        /// </summary>
        [Flags]
        public enum ImageListDrawItemConstants : int
        {
            /// <summary>
            /// Draw item normally.
            /// </summary>
            ILD_NORMAL = 0x0,
            /// <summary>
            /// Draw item transparently.
            /// </summary>
            ILD_TRANSPARENT = 0x1,
            /// <summary>
            /// Draw item blended with 25% of the specified foreground colour
            /// or the Highlight colour if no foreground colour specified.
            /// </summary>
            ILD_BLEND25 = 0x2,
            /// <summary>
            /// Draw item blended with 50% of the specified foreground colour
            /// or the Highlight colour if no foreground colour specified.
            /// </summary>
            ILD_SELECTED = 0x4,
            /// <summary>
            /// Draw the icon's mask
            /// </summary>
            ILD_MASK = 0x10,
            /// <summary>
            /// Draw the icon image without using the mask
            /// </summary>
            ILD_IMAGE = 0x20,
            /// <summary>
            /// Draw the icon using the ROP specified.
            /// </summary>
            ILD_ROP = 0x40,
            /// <summary>
            /// Preserves the alpha channel in dest. XP only.
            /// </summary>
            ILD_PRESERVEALPHA = 0x1000,
            /// <summary>
            /// Scale the image to cx, cy instead of clipping it.  XP only.
            /// </summary>
            ILD_SCALE = 0x2000,
            /// <summary>
            /// Scale the image to the current DPI of the display. XP only.
            /// </summary>
            ILD_DPISCALE = 0x4000
        }

        /// <summary>
        /// Flags specifying the state of the icon to draw from the Shell
        /// </summary>
        [Flags]
        public enum ShellIconStateConstants
        {
            /// <summary>
            /// Get icon in normal state
            /// </summary>
            ShellIconStateNormal = 0,
            /// <summary>
            /// Put a link overlay on icon 
            /// </summary>
            ShellIconStateLinkOverlay = 0x8000,
            /// <summary>
            /// show icon in selected state 
            /// </summary>
            ShellIconStateSelected = 0x10000,
            /// <summary>
            /// get open icon 
            /// </summary>
            ShellIconStateOpen = 0x2,
            /// <summary>
            /// apply the appropriate overlays
            /// </summary>
            ShellIconAddOverlays = 0x000000020,
        }
        #endregion

        #region Private Enumerations
        [Flags]
        private enum SHGetFileInfoConstants : int
        {
            SHGFI_ICON = 0x100,                // get icon 
            SHGFI_DISPLAYNAME = 0x200,         // get display name 
            SHGFI_TYPENAME = 0x400,            // get type name 
            SHGFI_ATTRIBUTES = 0x800,          // get attributes 
            SHGFI_ICONLOCATION = 0x1000,       // get icon location 
            SHGFI_EXETYPE = 0x2000,            // return exe type 
            SHGFI_SYSICONINDEX = 0x4000,       // get system icon index 
            SHGFI_LINKOVERLAY = 0x8000,        // put a link overlay on icon 
            SHGFI_SELECTED = 0x10000,          // show icon in selected state 
            SHGFI_ATTR_SPECIFIED = 0x20000,    // get only specified attributes 
            SHGFI_LARGEICON = 0x0,             // get large icon 
            SHGFI_SMALLICON = 0x1,             // get small icon 
            SHGFI_OPENICON = 0x2,              // get open icon 
            SHGFI_SHELLICONSIZE = 0x4,         // get shell size icon 
            SHGFI_PIDL = 0x8,                  // pszPath is a pidl 
            SHGFI_USEFILEATTRIBUTES = 0x10,    // use passed dwFileAttribute 
            SHGFI_ADDOVERLAYS = 0x000000020,   // apply the appropriate overlays
            SHGFI_OVERLAYINDEX = 0x000000040   // Get the index of the overlay
        }
        #endregion

        #region Special Folder Enum
        public enum SpecialFolders
        {
            MyComputer,
            MyDocuments,
            MyPictures,
            MyMusic,
            MyVideos,
            Desktop,
            Network,
            ControlPanel,
            RecycleBin,
            UserDirectory
        };

        public enum CSIDL
        {
            CSIDL_DESKTOP = 0x0000,    // <desktop>
            CSIDL_INTERNET = 0x0001,    // Internet Explorer (icon on desktop)
            CSIDL_PROGRAMS = 0x0002,    // Start Menu\Programs
            CSIDL_CONTROLS = 0x0003,    // My Computer\Control Panel
            CSIDL_PRINTERS = 0x0004,    // My Computer\Printers
            CSIDL_PERSONAL = 0x0005,    // My Documents
            CSIDL_FAVORITES = 0x0006,    // <user name>\Favorites
            CSIDL_STARTUP = 0x0007,    // Start Menu\Programs\Startup
            CSIDL_RECENT = 0x0008,    // <user name>\Recent
            CSIDL_SENDTO = 0x0009,    // <user name>\SendTo
            CSIDL_BITBUCKET = 0x000a,    // <desktop>\Recycle Bin
            CSIDL_STARTMENU = 0x000b,    // <user name>\Start Menu
            CSIDL_MYDOCUMENTS = 0x000c,    // logical "My Documents" desktop icon
            CSIDL_MYMUSIC = 0x000d,    // "My Music" folder
            CSIDL_MYVIDEO = 0x000e,    // "My Videos" folder
            CSIDL_DESKTOPDIRECTORY = 0x0010,    // <user name>\Desktop
            CSIDL_DRIVES = 0x0011,    // My Computer
            CSIDL_NETWORK = 0x0012,    // Network Neighborhood (My Network Places)
            CSIDL_NETHOOD = 0x0013,    // <user name>\nethood
            CSIDL_FONTS = 0x0014,    // windows\fonts
            CSIDL_TEMPLATES = 0x0015,
            CSIDL_COMMON_STARTMENU = 0x0016,    // All Users\Start Menu
            CSIDL_COMMON_PROGRAMS = 0X0017,    // All Users\Start Menu\Programs
            CSIDL_COMMON_STARTUP = 0x0018,    // All Users\Startup
            CSIDL_COMMON_DESKTOPDIRECTORY = 0x0019,    // All Users\Desktop
            CSIDL_APPDATA = 0x001a,    // <user name>\Application Data
            CSIDL_PRINTHOOD = 0x001b,    // <user name>\PrintHood
            CSIDL_LOCAL_APPDATA = 0x001c,    // <user name>\Local Settings\Applicaiton Data (non roaming)
            CSIDL_ALTSTARTUP = 0x001d,    // non localized startup
            CSIDL_COMMON_ALTSTARTUP = 0x001e,    // non localized common startup
            CSIDL_COMMON_FAVORITES = 0x001f,
            CSIDL_INTERNET_CACHE = 0x0020,
            CSIDL_COOKIES = 0x0021,
            CSIDL_HISTORY = 0x0022,
            CSIDL_COMMON_APPDATA = 0x0023,    // All Users\Application Data
            CSIDL_WINDOWS = 0x0024,    // GetWindowsDirectory()
            CSIDL_SYSTEM = 0x0025,    // GetSystemDirectory()
            CSIDL_PROGRAM_FILES = 0x0026,    // C:\Program Files
            CSIDL_MYPICTURES = 0x0027,    // C:\Program Files\My Pictures
            CSIDL_PROFILE = 0x0028,    // USERPROFILE
            CSIDL_SYSTEMX86 = 0x0029,    // x86 system directory on RISC
            CSIDL_PROGRAM_FILESX86 = 0x002a,    // x86 C:\Program Files on RISC
            CSIDL_PROGRAM_FILES_COMMON = 0x002b,    // C:\Program Files\Common
            CSIDL_PROGRAM_FILES_COMMONX86 = 0x002c,    // x86 Program Files\Common on RISC
            CSIDL_COMMON_TEMPLATES = 0x002d,    // All Users\Templates
            CSIDL_COMMON_DOCUMENTS = 0x002e,    // All Users\Documents
            CSIDL_COMMON_ADMINTOOLS = 0x002f,    // All Users\Start Menu\Programs\Administrative Tools
            CSIDL_ADMINTOOLS = 0x0030,    // <user name>\Start Menu\Programs\Administrative Tools
            CSIDL_CONNECTIONS = 0x0031,    // Network and Dial-up Connections
            CSIDL_COMMON_MUSIC = 0x0035,    // All Users\My Music
            CSIDL_COMMON_PICTURES = 0x0036,    // All Users\My Pictures
            CSIDL_COMMON_VIDEO = 0x0037,    // All Users\My Video
            CSIDL_CDBURN_AREA = 0x003b    // USERPROFILE\Local Settings\Application Data\Microsoft\CD Burning
        };
        #endregion

        #region Special Folder Dictionary
        public Dictionary<SpecialFolders, CSIDL> specialFoldersDictionary = new Dictionary<SpecialFolders, CSIDL>
        {
            {SpecialFolders.MyComputer, CSIDL.CSIDL_DRIVES},
            {SpecialFolders.MyDocuments, CSIDL.CSIDL_PERSONAL},
            {SpecialFolders.MyPictures, CSIDL.CSIDL_MYPICTURES},
            {SpecialFolders.MyMusic, CSIDL.CSIDL_MYMUSIC},
            {SpecialFolders.MyVideos, CSIDL.CSIDL_MYVIDEO},
            {SpecialFolders.Desktop, CSIDL.CSIDL_DESKTOP},
            {SpecialFolders.Network, CSIDL.CSIDL_NETWORK},
            {SpecialFolders.ControlPanel, CSIDL.CSIDL_CONTROLS},
            {SpecialFolders.RecycleBin, CSIDL.CSIDL_BITBUCKET},
            {SpecialFolders.UserDirectory, CSIDL.CSIDL_PROFILE},
            
        };
        #endregion

        public SystemImageListSize Size 
        {
            get{return size;}
            set{size = value;}
        }

        public Icons(SystemImageListSize size)
        {
            this.size = size;

            // Get the System IImageList object from the Shell:
            Guid iidImageList = new Guid("46EB5926-582E-4017-9FDF-E8998DAA0950");
            int ret = SHGetImageList((int)size, ref iidImageList, ref iImageList);

            // the image list handle is the IUnknown pointer, but 
            // using Marshal.GetIUnknownForObject doesn't return
            // the right value.  It really doesn't hurt to make
            // a second call to get the handle:
            SHGetImageListHandle((int)size, ref iidImageList, ref hIml);
        }

        #region Dispose Methods
        /// <summary>
        /// Clears up any resources associated with the SystemImageList
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        /// <summary>
        /// Clears up any resources associated with the SystemImageList
        /// when disposing is true.
        /// </summary>
        /// <param name="disposing">Whether the object is being disposed</param>
        public virtual void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (disposing)
                {
                    if (iImageList != null)
                    {
                        Marshal.ReleaseComObject(iImageList);
                    }
                    iImageList = null;
                }
            }
            disposed = true;
        }
        /// <summary>
        /// Finalise for SysImageList
        /// </summary>
        ~Icons()
        {
            Dispose(false);
        }	
        #endregion

        /// <summary>
        /// Return the icon for the specified file, always using 
        /// the cached version where possible.
        /// </summary>
        /// <param name="filePath">File path to get icon for</param>
        /// <returns>Icon for the file at the path</returns>
        public Icon GetIcon(string filePath)
        {
            int index = IconIndex(filePath, false, false, ShellIconStateConstants.ShellIconStateNormal);
            return Icon(index);
        }

        /// <summary>
        /// Return the icon for the specified file, always using 
        /// the cached version where possible.
        /// </summary>
        /// <param name="filePath">File path to get icon for</param>
        /// <param name="isLink">Flags specifying if the folder is a link</param>
        /// <returns>Icon for the file at the path</returns>
        public Icon GetIcon(string filePath, bool isLink)
        {
            int index = IconIndex(filePath, false, false, isLink ? ShellIconStateConstants.ShellIconStateLinkOverlay : ShellIconStateConstants.ShellIconStateNormal);
            return Icon(index);
        }

        /// <summary>
        /// Return the icon for a folder
        /// </summary>
        /// <returns>Icon for a folder</returns>
        public Icon GetFolderIcon()
        {
            int index = IconIndex(@"C:\Windows", false, true, ShellIconStateConstants.ShellIconStateNormal);
            return Icon(index);
        }

        /// <summary>
        /// Return the folder icon for the specified folder, always using 
        /// the cached version where possible.
        /// </summary>
        /// <param name="folderPath">Folder path to get icon for</param>
        /// <returns>Icon for the folder at the path</returns>
        public Icon GetFolderIcon(string folderPath)
        {
            int index = IconIndex(folderPath, false, true, ShellIconStateConstants.ShellIconStateNormal);
            return Icon(index);
        }

        /// <summary>
        /// Returns a GDI+ copy of the icon from the ImageList
        /// at the specified index.
        /// </summary>
        /// <param name="index">The index to get the icon for</param>
        /// <returns>The specified icon</returns>
        public Icon Icon(int index)
        {
            Icon icon = null;

            IntPtr hIcon = IntPtr.Zero;
            if (iImageList == null)
            {
                hIcon = ImageList_GetIcon(hIml, index, (int)ImageListDrawItemConstants.ILD_TRANSPARENT);
            }
            else
            {
                iImageList.GetIcon(index, (int)ImageListDrawItemConstants.ILD_TRANSPARENT, ref hIcon);
            }

            if (hIcon != IntPtr.Zero)
            {
                icon = System.Drawing.Icon.FromHandle(hIcon);
            }
            return icon;
        }

        /// <summary>
        /// Returns the index of the icon for the specified file
        /// </summary>
        /// <param name="fileName">Filename to get icon for</param>
        /// <param name="forceLoadFromDisk">If True, then hit the disk to get the icon,
        /// otherwise only hit the disk if no cached icon is available.</param>
        /// <param name="isDirectory">Flags specifying if the file is a directory.</param>
        /// <param name="isLink">Flags specifying if the file is a link</param>
        /// <param name="iconState">Flags specifying the state of the icon
        /// returned.</param>
        /// <returns>Index of the icon</returns>
        public int IconIndex(string fileName, bool forceLoadFromDisk, bool isDirectory, ShellIconStateConstants iconState)
        {
            SHGetFileInfoConstants dwFlags = SHGetFileInfoConstants.SHGFI_SYSICONINDEX;
            int dwAttr = 0;
            if (size == SystemImageListSize.SmallIcons)
            {
                dwFlags |= SHGetFileInfoConstants.SHGFI_SMALLICON;
            }

            // We can choose whether to access the disk or not. If you don't
            // hit the disk, you may get the wrong icon if the icon is
            // not cached. Also only works for files.
            if (!forceLoadFromDisk)
            {
                dwFlags |= SHGetFileInfoConstants.SHGFI_USEFILEATTRIBUTES;
                dwAttr = FILE_ATTRIBUTE_NORMAL;
            }

            if (isDirectory)
            {
                dwAttr = FILE_ATTRIBUTE_DIRECTORY;
            }

            SHFILEINFO shFileInfo = new SHFILEINFO();
            uint shfiSize = (uint)Marshal.SizeOf(shFileInfo.GetType());
            IntPtr retVal = SHGetFileInfo(fileName, dwAttr, ref shFileInfo, shfiSize, ((uint)(dwFlags) | (uint)iconState));

            if (retVal.Equals(IntPtr.Zero))
            {
                System.Diagnostics.Debug.Assert((!retVal.Equals(IntPtr.Zero)), "Failed to get icon index");
                return 0;
            }
            else
            {
                return shFileInfo.iIcon;
            }
        }

        /// <summary>
        /// Returns the icon for a special folder
        /// </summary>
        /// <param name="specialFolder">Special folder enum parameter</param>
        /// <returns>Icon of specified special folder</returns>
        public Icon GetSpecialFolderIcon(SpecialFolders specialFolder)
        {
            IntPtr computerPIDL = IntPtr.Zero;
            SHGetSpecialFolderLocation(IntPtr.Zero, specialFoldersDictionary[specialFolder], ref computerPIDL);

            SHFILEINFO shFileInfo = new SHFILEINFO();

            SHGetFileInfoConstants flags = SHGetFileInfoConstants.SHGFI_PIDL | SHGetFileInfoConstants.SHGFI_ICON | SHGetFileInfoConstants.SHGFI_SMALLICON;

            // sets shFileInfo.iIcon as the index, which is passed to the Icon method
            SHGetFileInfo(computerPIDL, 0, ref shFileInfo, (uint)Marshal.SizeOf(shFileInfo.GetType()), ((uint)(flags)));
            
            Marshal.FreeCoTaskMem(computerPIDL);

            return Icon(shFileInfo.iIcon);
        }
    }
}
